0%

LevelDB源码解析(五)- CURRENT和Manifest

Manifest文件,简单的看就是当前VersionEdit的持久化信息,其中包含了:

  1. Comparator:全局的比较方法
  2. LogNumber:下一个MemTable的WAL日志文件的FileNumber
  3. PreviousLogNumber:已经被废弃,之前版本有用到
  4. NextFileNumber:下一个File的数字
  5. LastSequence:最新的Seq
  6. Compact_Pointer:在Compact一章中统一讲。
  7. Deteted_Files:相对于上一个Version,删除的文件
  8. New_Files:相对于上一个Version,新增的文件

以上内容都在VersionEditTag中进行读写。

其中Manifest文件只会存在一个,但是名字中的FileNumber不是一定的,在数据目录下,文件名可能是

MANIFEST-000540。个人猜想可能是版本问题导致的。

初始化:

在DbIMPL进行初始化时,会创建VersionSet,在VersionSet的构造函数中,调用了initializeIfNeeded对Manifest进行了初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private void initializeIfNeeded() {
File currentFile = new File(databaseDir, Filename.currentFileName()); // 1
if (!currentFile.exists()) {
VersionEdit edit = new VersionEdit(); // 2
edit.setComparatorName(internalKeyComparator.name());
edit.setLogNumber(prevLogNumber);
edit.setNextFileNumber(nextFileNumber.get());
edit.setLastSequenceNumber(lastSequence);

LogWriter log = Logs.createLogWriter(new File(databaseDir, Filename.descriptorFileName(manifestFileNumber)), manifestFileNumber); // 3
try {
writeSnapshot(log); // 4
log.addRecord(edit.encode(), false); // 5
}
finally {
log.close();
}
Filename.setCurrentFile(databaseDir, log.getFileNumber()); // 5
}
}
  1. 找出名为”CURRENT”文件,前面提到这个文件的内容就是Manifest文件的文件名。
  2. 这里CURRENT文件不存在,所以new一个VersionEdit,其中prevLogNumber是0,nextFileNumber是2,lastSeq也是0。
  3. 这里的manifestFileNumber文件是1,这里知道为什么nextFileNumber默认是2开始了吧,因为1是ManifestFile的初始化FileNumber
  4. 这个方法里,new了一个VersionEdit,基本上什么值也没设,这里不知道为什么要先把这个VersionEdit放进去。
  5. 把上面的VersionEdit写入到Manifest中
  6. 这里把上面的Manifest文件名,写入CURRENT文件,这个方法中用到了Temp文件。

每次Compact过后,就会调用VersionSet的logAndApply方法,把Edit的信息传入,加入到Manifest文件中。

这个方法下面会详细描述。

那么一个疑问就来了,每次Compact后都会往里面Append新的VersionEdit信息,那么这个文件不就会越来越大吗?就像Redis的Compact一样?是不是有什么机制,会导致创建新的Manifest文件,把当前的Version的快照放进去,然后丢弃旧的Manifest文件呢?

答案是有的:其中每次启动后,触发的第一次Compact,会导致旧的Manifest文件被丢弃,生成新的Manifest文件,把当前Version的快照信息放入。

这里其实有个问题的,只有每次重新启动后才会触发,如果一直在运行的话,其实不会触发重新清理的。

虽然在运行中并不会去读取这个Manifest文件,但是下次启动恢复Version信息时,需要从头到尾遍历这个文件,速度可能会很慢。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public void logAndApply(VersionEdit edit) {
//...
boolean createdNewManifest = false;
try {
if (descriptorLog == null) { // 1
edit.setNextFileNumber(nextFileNumber.get());
descriptorLog = Logs.createLogWriter(new File(databaseDir, Filename.descriptorFileName(manifestFileNumber)), manifestFileNumber); // 2
writeSnapshot(descriptorLog); // 3
createdNewManifest = true;
}

Slice record = edit.encode();
descriptorLog.addRecord(record, true);

if (createdNewManifest) {
Filename.setCurrentFile(databaseDir, descriptorLog.getFileNumber()); // 4
}
} catch (IOException e) {
//...
}
}


private void writeSnapshot(LogWriter log) {
// Save metadata
VersionEdit edit = new VersionEdit();
edit.setComparatorName(internalKeyComparator.name());

// Save compaction pointers
edit.setCompactPointers(compactPointers);

// Save files
edit.addFiles(current.getFiles());

Slice record = edit.encode();
log.addRecord(record, false);
}
  1. descriptorLog除了在这个方法,没有在其他地方被赋值过,所以第一次进来,肯定是null的
  2. 创建一个新的Manifest文件
  3. 调用writeSnapshot方法,生成VersionEdit,把当前所有的文件写入Manifest文件
  4. 设置CURRENT文件,指向新的Manifest文件

VersionSet的恢复:

前面提到过一个公式:OldVersion + VersionEdit = NewVersion

如果重新启动应用,要恢复到最新的Version,只要把Manifest文件中的VersionEdit全部apply一遍就行了。

方法在VersionSet::recover中,代码浅显易懂,这里就不展开了。